using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using KgUtils.Resources;
using System.Diagnostics;
using System.Web.UI.WebControls;

namespace KgUtils.Collections
{
    /// <summary>
    /// Extension applied to IEnumerables
    /// Most methods inspired from dvp.net http://dvp-net.developpez.com/
    /// </summary>
    public static partial class IEnumerableExtension
    {
        /// <summary>
        /// Indique si une énumération est nulle ou vide.
        /// </summary>
        /// <typeparam name="TSource">Le type de l'énumération.</typeparam>
        /// <param name="source">L'énumération à tester.</param>
        /// <returns></returns>
        public static bool IsNullOrEmpty<TSource>(this IEnumerable<TSource> source)
        {
            return (source == null || !source.Any());
        }

        /// <summary>
        /// Ajoute un élément à la suite de la séquence spécifiée
        /// </summary>
        /// <typeparam name="T">Type des éléments de la séquence</typeparam>
        /// <param name="source">Séquence d'origine</param>
        /// <param name="item">Elément à ajouter</param>
        /// <returns>La séquence d'origine suivie de l'élément spécifiée</returns>
        public static IEnumerable<T> Append<T>(this IEnumerable<T> source, T item)
        {
            return source.Concat(new[] { item });
        }

        /// <summary>
        /// Insère un élément au début de la séquence spécifiée
        /// </summary>
        /// <typeparam name="T">Type des éléments de la séquence</typeparam>
        /// <param name="source">Séquence d'origine</param>
        /// <param name="item">Elément à insérer</param>
        /// <returns>La séquence d'origine précédée de l'élément spécifiée</returns>
        public static IEnumerable<T> Prepend<T>(this IEnumerable<T> source, T item)
        {
            return new[] { item }.Concat(source);
        }

        public static IEnumerable<T> SortBy<T>(this IEnumerable<T> source, string propertyName, SortDirection direction)
        {
            return source.AsQueryable<T>().SortBy<T>(propertyName, direction);
        }

        #region None

        /// <summary>
        /// Indique si une séquence ne contient aucun élément
        /// </summary>
        /// <typeparam name="T">Type des éléments de la séquence</typeparam>
        /// <param name="source">Séquence à vérifier</param>
        /// <returns>true si <c>source</c> ne contient aucun élément ; sinon, false</returns>
        public static bool None<T>(this IEnumerable<T> source)
        {
            return !source.Any();
        }

        /// <summary>
        /// Indique s'il n'existe dans la séquence aucun élément qui satisfait le prédicat
        /// </summary>
        /// <typeparam name="T">Type des éléments de la séquence</typeparam>
        /// <param name="source">Séquence à vérifier</param>
        /// <param name="predicate">Prédicat à évaluer pour chaque élément de la séquence</param>
        /// <returns>true si la séquence ne contient aucun élément qui satisfait le prédicat ; sinon, false</returns>
        public static bool None<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            return !source.Any(predicate);
        }

        #endregion

        #region ContainsAny

        /// <summary>
        /// Indique si une séquence contient au moins un des éléments spécifiés, en utilisant le comparateur par défaut
        /// </summary>
        /// <typeparam name="T">Type des élements de la séquence</typeparam>
        /// <param name="source">Séquence dans laquelle rechercher des éléments</param>
        /// <param name="items">Eléments à rechercher</param>
        /// <returns>true si <c>source</c> contient au moins un des éléments de <c>items</c>, false sinon</returns>
        public static bool ContainsAny<T>(this IEnumerable<T> source, IEnumerable<T> items)
        {
            return ContainsAny(source, items, EqualityComparer<T>.Default);
        }

        /// <summary>
        /// Indique si une séquence contient au moins un des éléments spécifiés, en utilisant le comparateur spécifié
        /// </summary>
        /// <typeparam name="T">Type des élements de la séquence</typeparam>
        /// <param name="source">Séquence dans laquelle rechercher des éléments</param>
        /// <param name="items">Eléments à rechercher</param>
        /// <param name="comparer">Comparateur à utiliser</param>
        /// <returns>true si <c>source</c> contient au moins un des éléments de <c>items</c>, false sinon</returns>
        public static bool ContainsAny<T>(this IEnumerable<T> source, IEnumerable<T> items, IEqualityComparer<T> comparer)
        {
            return source.Intersect(items, comparer).Any();
        }

        #endregion

        #region AddRange

        /// <summary>
        /// Ajoute plusieurs éléments à une collection
        /// </summary>
        /// <typeparam name="T">Type des éléments de la collection</typeparam>
        /// <param name="collection">Collection à laquelle ajouter des éléments</param>
        /// <param name="items">Eléments à ajouter</param>
        public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
        {
            Guard.AssertNotNull(items, "items");
            foreach (var item in items)
            {
                collection.Add(item);
            }
        }

        #endregion

        #region IndexOf

        /// <summary>
        /// Renvoie l'index de la première occurence de l'élément spécifié dans la séquence
        /// </summary>
        /// <typeparam name="T">Le type des éléments de la séquence</typeparam>
        /// <param name="source">La séquence dans laquelle chercher l'élément</param>
        /// <param name="item">L'élément à rechercher</param>
        /// <returns>L'index de la première occurence de l'élément dans la séquence, ou -1 si l'élément ne s'y trouve pas.</returns>
        public static int IndexOf<T>(this IEnumerable<T> source, T item)
        {
            return source.AsIndexed()
                        .Where(x => x.Value.SafeEquals(item))
                        .Select(x => x.Index)
                        .FirstOrDefault(-1);
        }

        /// <summary>
        /// Renvoie l'index du premier élément pour lequel le prédicat spécifié est vérifié
        /// </summary>
        /// <typeparam name="T">Le type des éléments de la séquence</typeparam>
        /// <param name="source">La séquence dans laquelle chercher l'élément</param>
        /// <param name="predicate">Le prédicat à évaluer</param>
        /// <returns>L'index du premier élément pour lequel le prédicat est vérifié, ou -1 si aucun élément ne vérifie le prédicat.</returns>
        public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            return source.AsIndexed()
                        .Where(x => predicate(x.Value))
                        .Select(x => x.Index)
                        .FirstOrDefault(-1);
        }

        /// <summary>
        /// Renvoie l'index de la dernière occurence de l'élément spécifié dans la séquence
        /// </summary>
        /// <typeparam name="T">Le type des éléments de la séquence</typeparam>
        /// <param name="source">La séquence dans laquelle chercher l'élément</param>
        /// <param name="item">L'élément à rechercher</param>
        /// <returns>L'index de la dernière occurence de l'élément dans la séquence, ou -1 si l'élément ne s'y trouve pas.</returns>
        public static int LastIndexOf<T>(this IEnumerable<T> source, T item)
        {
            return source.AsIndexed()
                        .Where(x => x.Value.SafeEquals(item))
                        .Select(x => x.Index)
                        .LastOrDefault(-1);
        }

        /// <summary>
        /// Renvoie l'index du dernier élément pour lequel le prédicat spécifié est vérifié
        /// </summary>
        /// <typeparam name="T">Le type des éléments de la séquence</typeparam>
        /// <param name="source">La séquence dans laquelle chercher l'élément</param>
        /// <param name="predicate">Le prédicat à évaluer</param>
        /// <returns>L'index du dernier élément pour lequel le prédicat est vérifié, ou -1 si aucun élément ne vérifie le prédicat.</returns>
        public static int LastIndexOf<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            return source.AsIndexed()
                        .Where(x => predicate(x.Value))
                        .Select(x => x.Index)
                        .LastOrDefault(-1);
        }

        #endregion

        #region AsIndexed, Unindex

        /// <summary>
        /// Associe un index à un élément d'une séquence
        /// </summary>
        /// <typeparam name="T">Type des éléments de la séquence</typeparam>
        public class IndexedItem<T>
        {
            internal IndexedItem(int index, T value)
            {
                this.Index = index;
                this.Value = value;
            }

            /// <summary>
            /// Index de l'élément
            /// </summary>
            public int Index { get; private set; }

            /// <summary>
            /// Valeur de l'élément
            /// </summary>
            public T Value { get; private set; }
        }

        /// <summary>
        /// Associe à chaque élément de la séquence son index dans la séquence
        /// </summary>
        /// <typeparam name="T">Type des éléments de la séquence</typeparam>
        /// <param name="source">Séquence d'éléments à indexer</param>
        /// <returns>Une séquence projetée où chaque élément est associé à son index</returns>
        public static IEnumerable<IndexedItem<T>> AsIndexed<T>(this IEnumerable<T> source)
        {
            return source.Select((v, i) => new IndexedItem<T>(i, v));
        }

        /// <summary>
        /// Dissocie chaque élément de la séquence de son index. Cette méthode effectue l'opération inverse de AsIndexed.
        /// </summary>
        /// <typeparam name="T">Type des éléments de la séquence</typeparam>
        /// <param name="source">Séquence d'éléments à dissocier de leur index</param>
        /// <returns>Une séquence d'éléments sans leur index</returns>
        public static IEnumerable<T> Unindex<T>(this IEnumerable<IndexedItem<T>> source)
        {
            return source.Select(x => x.Value);
        }

        #endregion

        #region Apply

        /// <summary>
        /// Applique l'action spécifiée "au passage" sur chaque élément de la séquence
        /// </summary>
        /// <typeparam name="T">Type des éléments de la séquence</typeparam>
        /// <param name="source">Séquence d'origine</param>
        /// <param name="action">Action à effectuer sur chaque élément de la séquence</param>
        /// <returns>La séquence d'origine</returns>
        public static IEnumerable<T> Apply<T>(this IEnumerable<T> source, Action<T> action)
        {
            Guard.AssertNotNull(action, "action");
            return source.ScanIterator<T>(action);
        }

        private static IEnumerable<T> ScanIterator<T>(this IEnumerable<T> source, Action<T> scanner)
        {
            foreach (var item in source)
            {
                scanner(item);
                yield return item;
            }
        }

        #endregion

        #region FirstOrDefault, LastOrDefault, ElementAtOrDefault, SingleOrDefault, SingleOrFallback

        /// <summary>
        /// Retourne le premier élément d'une séquence, ou la valeur par défaut spécifiée si la séquence ne contient aucun élément.
        /// </summary>
        /// <typeparam name="T">Type des éléments de source</typeparam>
        /// <param name="source">Séquence à partir de laquelle retourner le premier élément</param>
        /// <param name="defaultValue">La valeur par défaut à renvoyer si la séquence est vide</param>
        /// <returns>Le premier élément de source s'il existe, sinon la valeur par défaut spécifiée</returns>
        public static T FirstOrDefault<T>(this IEnumerable<T> source, T defaultValue)
        {
            return source.DefaultIfEmpty(defaultValue).First();
        }

        /// <summary>
        /// Retourne le premier élément de la séquence à satisfaire à une condition, ou la valeur par défaut spécifiée
        /// si aucun élément correspondant n'est trouvé.
        /// </summary>
        /// <typeparam name="T">Type des éléments de source</typeparam>
        /// <param name="source">Séquence à partir de laquelle retourner un élément</param>
        /// <param name="predicate">Condition à vérifier</param>
        /// <param name="defaultValue">La valeur par défaut à renvoyer si aucun élément ne satisfait la condition</param>
        /// <returns>Le premier élément de source à satisfaire la condition, ou la valeur par défaut spécifiée si aucun
        /// élément ne satisfait la condition</returns>
        public static T FirstOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate, T defaultValue)
        {
            return source.Where(predicate).FirstOrDefault(defaultValue);
        }

        /// <summary>
        /// Retourne le dernier élément d'une séquence, ou la valeur par défaut spécifiée si la séquence ne contient aucun élément.
        /// </summary>
        /// <typeparam name="T">Type des éléments de source</typeparam>
        /// <param name="source">Séquence à partir de laquelle retourner le dernier élément</param>
        /// <param name="defaultValue">La valeur par défaut à renvoyer si la séquence est vide</param>
        /// <returns>Le dernier élément de source s'il existe, sinon la valeur par défaut spécifiée</returns>
        public static T LastOrDefault<T>(this IEnumerable<T> source, T defaultValue)
        {
            return source.DefaultIfEmpty(defaultValue).Last();
        }

        /// <summary>
        /// Retourne le dernier élément de la séquence à satisfaire à une condition, ou la valeur par défaut spécifiée
        /// si aucun élément correspondant n'est trouvé.
        /// </summary>
        /// <typeparam name="T">Type des éléments de source</typeparam>
        /// <param name="source">Séquence à partir de laquelle retourner un élément</param>
        /// <param name="predicate">Condition à vérifier</param>
        /// <param name="defaultValue">La valeur par défaut à renvoyer si aucun élément ne satisfait la condition</param>
        /// <returns>Le dernier élément de source à satisfaire la condition, ou la valeur par défaut spécifiée si aucun
        /// élément ne satisfait la condition</returns>
        public static T LastOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate, T defaultValue)
        {
            return source.Where(predicate).LastOrDefault(defaultValue);
        }

        /// <summary>
        /// Retourne l'élément situé à un index spécifié dans une séquence, ou la valeur par défaut spécifiée si l'index est hors limites.
        /// </summary>
        /// <typeparam name="T">Type des éléments de source</typeparam>
        /// <param name="source">Séquence à partir de laquelle retourner un élément</param>
        /// <param name="index">Index de l'élément à récupérer</param>
        /// <param name="defaultValue">Valeur par défaut à renvoyer si l'index est hors-limite</param>
        /// <returns>L'élément à l'index spécifié s'il existe ; sinon, la valeur par défaut spécifiée.</returns>
        public static T ElementAtOrDefault<T>(this IEnumerable<T> source, int index, T defaultValue)
        {
            Guard.AssertRange(index, "index", 0, int.MaxValue);
            return source.Skip(index).DefaultIfEmpty(defaultValue).First();
        }

        /// <summary>
        /// Retourne l'unique élément d'une séquence, ou la valeur par défaut spécifiée si la séquence n'a aucun élément.
        /// Lève une exception si la séquence contient plus d'un élément
        /// </summary>
        /// <typeparam name="T">Type des éléments de source</typeparam>
        /// <param name="source">Séquence à partir de laquelle retourner un élément</param>
        /// <param name="defaultValue">Valeur par défaut à retourner si la séquence est vide</param>
        /// <returns>L'unique élément de la séquence, ou la valeur par défaut spécifiée si la séquence est vide</returns>
        public static T SingleOrDefault<T>(this IEnumerable<T> source, T defaultValue)
        {
            T value = defaultValue;

            if (source.Count() == 1)
                value = source.First();
            else
                throw new InvalidOperationException(Messages.InputSequenceHasMoreThanOneElement);

            return value;
        }

        /// <summary>
        /// Retourne l'unique élément d'une séquence qui satisfait une condition, ou la valeur par défaut
        /// spécifiée si aucun élément ne satisfait la condition.
        /// Lève une exception si plusieurs éléments satisfont la condition.
        /// </summary>
        /// <typeparam name="T">Type des éléments de source</typeparam>
        /// <param name="source">Séquence à partir de laquelle retourner un élément</param>
        /// <param name="predicate">La condition à vérifier</param>
        /// <param name="defaultValue">Valeur par défaut à retourner si aucun élément ne satisfait la condition</param>
        /// <returns>L'unique élément de la séquence, ou la valeur par défaut spécifiée si aucun élément ne satisfait la condition</returns>
        public static T SingleOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate, T defaultValue)
        {
            return source.Where(predicate).SingleOrDefault(defaultValue);
        }

        /// <summary>
        /// Returns the single element in the given sequence, or the result
        /// of executing a fallback delegate if the sequence is empty.
        /// This method throws an exception if there is more than one element in the sequence.
        /// </summary>
        /// <remarks>
        /// The fallback delegate is not executed if the sequence is non-empty.
        /// This operator uses immediate execution and has optimizations for <see cref="IList{T}"/> sources.
        /// </remarks>
        /// <typeparam name="TSource">Element type of sequence</typeparam>
        /// <param name="source">The source sequence</param>
        /// <param name="fallback">The fallback delegate to execute if the sequence is empty</param>
        /// <exception cref="ArgumentNullException">source or fallback is null</exception>
        /// <exception cref="InvalidOperationException">The sequence has more than one element</exception>
        /// <returns>The single element in the sequence, or the result of calling the
        /// fallback delegate if the sequence is empty.</returns>

        public static TSource SingleOrFallback<TSource>(this IEnumerable<TSource> source, Func<TSource> fallback)
        {
            Guard.AssertNotNull(source, "source");
            Guard.AssertNotNull(fallback, "fallback");

            IList<TSource> list = source as IList<TSource>;
            if (list != null)
            {
                switch (list.Count)
                {
                    case 0:
                        return fallback();

                    case 1:
                        return list[0];

                    // anything but 0 and 1 is not handled
                }
            }
            else
            {
                using (IEnumerator<TSource> iterator = source.GetEnumerator())
                {
                    if (!iterator.MoveNext())
                    {
                        return fallback();
                    }
                    TSource first = iterator.Current;

                    // Return if there's no next element
                    if (!iterator.MoveNext())
                    {
                        return first;
                    }
                }
            }

            // We should have checked the sequence length and returned by now
            throw new InvalidOperationException("Sequence contains more than one element");
        }

        #endregion

        #region MaxBy, MinBy
        /// <summary>
        /// Returns the maximal element of the given sequence, based on
        /// the given projection.
        /// </summary>
        /// <remarks>
        /// If more than one element has the maximal projected value, the first
        /// one encountered will be returned. This overload uses the default comparer
        /// for the projected type. This operator uses immediate execution, but
        /// only buffers a single result (the current maximal element).
        /// </remarks>
        /// <typeparam name="TSource">Type of the source sequence</typeparam>
        /// <typeparam name="TKey">Type of the projected element</typeparam>
        /// <param name="source">Source sequence</param>
        /// <param name="selector">Selector to use to pick the results to compare</param>
        /// <returns>The maximal element, according to the projection.</returns>
        public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
        {
            return source.MaxBy(selector, Comparer<TKey>.Default);
        }

        /// <summary>
        /// Returns the maximal element of the given sequence, based on
        /// the given projection and the specified comparer for projected values. 
        /// </summary>
        /// <remarks>
        /// If more than one element has the maximal projected value, the first
        /// one encountered will be returned. This overload uses the default comparer
        /// for the projected type. This operator uses immediate execution, but
        /// only buffers a single result (the current maximal element).
        /// </remarks>
        /// <typeparam name="TSource">Type of the source sequence</typeparam>
        /// <typeparam name="TKey">Type of the projected element</typeparam>
        /// <param name="source">Source sequence</param>
        /// <param name="selector">Selector to use to pick the results to compare</param>
        /// <param name="comparer">Comparer to use to compare projected values</param>
        /// <returns>The maximal element, according to the projection.</returns>        
        public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IComparer<TKey> comparer)
        {
            using (IEnumerator<TSource> sourceIterator = source.GetEnumerator())
            {
                if (!sourceIterator.MoveNext())
                {
                    throw new InvalidOperationException("Sequence was empty");
                }
                TSource max = sourceIterator.Current;
                TKey maxKey = selector(max);
                while (sourceIterator.MoveNext())
                {
                    TSource candidate = sourceIterator.Current;
                    TKey candidateProjected = selector(candidate);
                    if (comparer.Compare(candidateProjected, maxKey) > 0)
                    {
                        max = candidate;
                        maxKey = candidateProjected;
                    }
                }
                return max;
            }
        }

        /// <summary>
        /// Returns the minimal element of the given sequence, based on
        /// the given projection.
        /// </summary>
        /// <remarks>
        /// If more than one element has the minimal projected value, the first
        /// one encountered will be returned. This overload uses the default comparer
        /// for the projected type. This operator uses immediate execution, but
        /// only buffers a single result (the current minimal element).
        /// </remarks>
        /// <typeparam name="TSource">Type of the source sequence</typeparam>
        /// <typeparam name="TKey">Type of the projected element</typeparam>
        /// <param name="source">Source sequence</param>
        /// <param name="selector">Selector to use to pick the results to compare</param>
        /// <returns>The minimal element, according to the projection.</returns>

        public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
        {
            return source.MinBy(selector, Comparer<TKey>.Default);
        }

        /// <summary>
        /// Returns the minimal element of the given sequence, based on
        /// the given projection and the specified comparer for projected values.
        /// </summary>
        /// <remarks>
        /// If more than one element has the minimal projected value, the first
        /// one encountered will be returned. This overload uses the default comparer
        /// for the projected type. This operator uses immediate execution, but
        /// only buffers a single result (the current minimal element).
        /// </remarks>
        /// <typeparam name="TSource">Type of the source sequence</typeparam>
        /// <typeparam name="TKey">Type of the projected element</typeparam>
        /// <param name="source">Source sequence</param>
        /// <param name="selector">Selector to use to pick the results to compare</param>
        /// <param name="comparer">Comparer to use to compare projected values</param>
        /// <returns>The minimal element, according to the projection.</returns>

        public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IComparer<TKey> comparer)
        {
            using (IEnumerator<TSource> sourceIterator = source.GetEnumerator())
            {
                if (!sourceIterator.MoveNext())
                {
                    throw new InvalidOperationException("Sequence was empty");
                }
                TSource min = sourceIterator.Current;
                TKey minKey = selector(min);
                while (sourceIterator.MoveNext())
                {
                    TSource candidate = sourceIterator.Current;
                    TKey candidateProjected = selector(candidate);
                    if (comparer.Compare(candidateProjected, minKey) < 0)
                    {
                        min = candidate;
                        minKey = candidateProjected;
                    }
                }
                return min;
            }
        }

        #endregion

        #region ForEach
        /// <summary>
        /// Immediately executes the given action on each element in the source sequence.
        /// </summary>
        /// <typeparam name="T">The type of the elements in the sequence</typeparam>
        /// <param name="source">The sequence of elements</param>
        /// <param name="action">The action to execute on each element</param>
        public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
        {
            Guard.AssertNotNull(source, "source");
            Guard.AssertNotNull(action, "action");
            foreach (T element in source)
            {
                action(element);
            }
        }

        #endregion

        #region Pad
        /// <summary>
        /// Pads a sequence with default values if it is narrower (shorter 
        /// in length) than a given width.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements of <paramref name="source"/>.</typeparam>
        /// <param name="source">The sequence to pad.</param>
        /// <param name="width">The width/length below which to pad.</param>
        /// <returns>
        /// Returns a sequence that is at least as wide/long as the width/length
        /// specified by the <paramref name="width"/> parameter.
        /// </returns>
        /// <remarks>
        /// This operator uses deferred execution and streams its results.
        /// </remarks>
        /// <example>
        /// <code>
        /// int[] numbers = { 123, 456, 789 };
        /// IEnumerable&lt;int&gt; result = numbers.Pad(5);
        /// </code>
        /// The <c>result</c> variable, when iterated over, will yield 
        /// 123, 456, 789 and two zeroes, in turn.
        /// </example>

        public static IEnumerable<TSource> Pad<TSource>(this IEnumerable<TSource> source, int width)
        {
            return Pad(source, width, default(TSource));
        }

        /// <summary>
        /// Pads a sequence with a given filler value if it is narrower (shorter 
        /// in length) than a given width.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements of <paramref name="source"/>.</typeparam>
        /// <param name="source">The sequence to pad.</param>
        /// <param name="width">The width/length below which to pad.</param>
        /// <param name="padding">The value to use for padding.</param>
        /// <returns>
        /// Returns a sequence that is at least as wide/long as the width/length
        /// specified by the <paramref name="width"/> parameter.
        /// </returns>
        /// <remarks>
        /// This operator uses deferred execution and streams its results.
        /// </remarks>
        /// <example>
        /// <code>
        /// int[] numbers = { 123, 456, 789 };
        /// IEnumerable&lt;int&gt; result = numbers.Pad(5, -1);
        /// </code>
        /// The <c>result</c> variable, when iterated over, will yield 
        /// 123, 456, and 789 followed by two occurrences of -1, in turn.
        /// </example>

        public static IEnumerable<TSource> Pad<TSource>(this IEnumerable<TSource> source, int width, TSource padding)
        {
            Guard.AssertRange(width, "width", 0, int.MaxValue);
            return PadImpl(source, width, padding, null);
        }

        /// <summary>
        /// Pads a sequence with a dynamic filler value if it is narrower (shorter 
        /// in length) than a given width.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements of <paramref name="source"/>.</typeparam>
        /// <param name="source">The sequence to pad.</param>
        /// <param name="width">The width/length below which to pad.</param>
        /// <param name="paddingSelector">Function to calculate padding.</param>
        /// <returns>
        /// Returns a sequence that is at least as wide/long as the width/length
        /// specified by the <paramref name="width"/> parameter.
        /// </returns>
        /// <remarks>
        /// This operator uses deferred execution and streams its results.
        /// </remarks>
        /// <example>
        /// <code>
        /// int[] numbers = { 0, 1, 2 };
        /// IEnumerable&lt;int&gt; result = numbers.Pad(5, i => -i);
        /// </code>
        /// The <c>result</c> variable, when iterated over, will yield 
        /// 0, 1, 2, -3 and -4, in turn.
        /// </example>

        public static IEnumerable<TSource> Pad<TSource>(this IEnumerable<TSource> source, int width, Func<int, TSource> paddingSelector)
        {
            Guard.AssertRange(width, "width", 0, int.MaxValue);
            return PadImpl(source, width, default(TSource), paddingSelector);
        }

        private static IEnumerable<T> PadImpl<T>(IEnumerable<T> source, int width, T padding, Func<int, T> paddingSelector)
        {
            Guard.AssertNotNull(source, "source");
            Guard.AssertRange(width, "width", 0, int.MaxValue);

            var count = 0;
            foreach (var item in source)
            {
                yield return item;
                count++;
            }
            while (count < width)
            {
                yield return paddingSelector != null ? paddingSelector(count) : padding;
                count++;
            }
        }

        #endregion
    }
}
